import { getIps } from "albert.gao.mkcert";
import killPort from "cross-port-killer";
import Express from "express";
import ExpressWs from "express-ws";
import { getUnOccupiedPort } from "./network";
import type { Server } from "ws";

export type CreateExpressWsResult = {
  /** Express的app */
  app: Express.Express;
  // use: Express.Express["use"];
  /** 经过变更后的port */
  port: number;
  /** WebSocket Server */
  wss: Server;
};

export interface ExpressAppSetupFunc {
  ({ app, port }: CreateExpressWsResult): void | Promise<void>;
}

export type CreateExpressWsOptions = {
  /** 初始指定一个端口， 默认值为8080， port之后后有可能会变更 */
  port?: number;
  /** 设置app.use系列 */
  setups?: ExpressAppSetupFunc[];
  /** 是否使用跨域，默认为true */
  CORS?: boolean;
  /**
   * 端口模式
   * @"change-when-occupied" 如果端口被占用，自动分配一个新的端口
   * @"kill-when-occupied" 如果端口被占用，尝试kill掉该端口
   * @ null 默认值，不控制端口
   */
  portMode?: "change-when-occupied" | "kill-when-occupied" | null;
  /** 是否立即listen */
  listen?: true;
};

export async function createExpressWs({
  port = 8080,
  setups = [],
  CORS = true,
  portMode = "change-when-occupied",
  listen = true,
}: CreateExpressWsOptions): Promise<CreateExpressWsResult> {
  if (portMode === "change-when-occupied") {
    port = await getUnOccupiedPort(port);
  } else if (portMode === "kill-when-occupied") {
    await killPort(port);
  }
  const app = Express();
  app.use(Express.json({ limit: "100mb" }));
  app.use(Express.urlencoded({ extended: true, limit: "100mb" }));
  if (CORS) {
    app.use((req, res, next) => {
      // Access-Control-Allow-Credentials
      res.set({
        "Access-Control-Allow-Credentials": true,
        "Access-Control-Max-Age": 1728000,
        "Access-Control-Allow-Origin": req.headers.origin || "*",
        "Access-Control-Allow-Headers": "X-Requested-With,Content-Type",
        "Access-Control-Allow-Methods": "PUT,POST,GET,DELETE,OPTIONS",
        // "Content-Type": "application/json; charset=utf-8",
      });
      req.method === "OPTIONS" ? res.status(204).end() : next();
    });
  }
  const ws = ExpressWs(app);
  const wss = ws.getWss();
  for (const func of setups) {
    await func({ app, port, wss });
  }
  if (listen) {
    app.listen(port);
    for (const ip of getIps()) {
      console.log("Express app listening at: " + `http://${ip}:${port}`);
    }
  }
  return { port, app, wss };
}
